package com.tianji.promotion.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.constants.PromotionConstants;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponDetailVO;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ObtainType;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author itheima
 * @since 2023-07-01
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    @Autowired
    private ICouponScopeService scopeService;


    @Override
    public void saveCoupon(CouponFormDTO dto) {
        // 1.保存优惠券
        // 1.1.转PO
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        // 1.2.保存
        save(coupon);
        if (!dto.getSpecific()) {
            // 没有范围限定
            return;
        }
        Long couponId = coupon.getId();
        // 2.保存限定范围
        List<Long> scopes = dto.getScopes();
        if (CollUtils.isEmpty(scopes)) {
            throw new BadRequestException("限定范围不能为空");
        }
        // 2.1.转换PO
        List<CouponScope> list = scopes.stream()
                //type 1-限指定分类 2-限指定课程
                .map(bizId -> new CouponScope().setBizId(bizId).setCouponId(couponId).setType(1))
                .collect(Collectors.toList());
        // 2.2.保存
        scopeService.saveBatch(list);
    }

    @Override
    public PageDTO<CouponPageVO> queryCouponByPage(CouponQuery query) {
        Integer status = query.getStatus();
        String name = query.getName();
        Integer type = query.getType();
        // 1.分页查询
        Page<Coupon> queryPage = new Page<>(query.getPageNo(), query.getPageSize());
        Page<Coupon> page = lambdaQuery()
                .eq(type != null, Coupon::getDiscountType, type)
                .eq(status != null, Coupon::getStatus, status)
                .like(StringUtils.isNotBlank(name), Coupon::getName, name)
                .orderByDesc(Coupon::getUpdateTime)
                .page(queryPage);
        // 2.处理VO
        List<Coupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        List<CouponPageVO> list = BeanUtils.copyList(records, CouponPageVO.class); //需要两个类的字段名完全一样
        // 3.返回
        return PageDTO.of(page, list);
    }

    @Override
    public CouponDetailVO queryCouponById(Long id) {
        // 1.查询优惠券
        Coupon coupon = getById(id);
        if (coupon == null) {
            throw new BadRequestException("优惠券信息不存在");
        }

        // 2.数据转换
        return BeanUtils.toBean(coupon, CouponDetailVO.class);
    }

    @Autowired
    private IExchangeCodeService codeService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //发放优惠券
    @Override
    public void beginIssue(CouponIssueFormDTO issue) {
        //1. 查询优惠券信息，校验状态
        Coupon localCoupon = this.getById(issue.getId());
        if(ObjectUtil.isNull(localCoupon)){
            throw new BadRequestException("优惠券信息不存在");
        }
        //(必须是待发放-1.已暂停-5)
        if(localCoupon.getStatus() != CouponStatus.DRAFT && localCoupon.getStatus() != CouponStatus.PAUSE){
            throw new BizIllegalException("优惠券状态不合法");
        }
        //2. dto->po
        Coupon coupon = BeanUtils.copyBean(issue, Coupon.class);
        //3. 判断是否立即发放
        LocalDateTime issueBeginTime = issue.getIssueBeginTime();
        LocalDateTime now = LocalDateTime.now();
        //如果开始时间为空就意味着立即发放
        if(ObjectUtil.isNull(issueBeginTime)){
            coupon.setStatus(CouponStatus.ISSUING);//发放中
            coupon.setIssueEndTime(now);//立即发放时间写当前即可
        } else {
            coupon.setStatus(CouponStatus.UN_ISSUE);
        }
        //4.更新数据库
        this.updateById(coupon);

        //添加缓存，前提是立刻发放
        boolean isBegin = issueBeginTime == null || !issueBeginTime.isAfter(now);
        if(isBegin){
            localCoupon.setIssueBeginTime(coupon.getIssueBeginTime());
            localCoupon.setIssueEndTime(coupon.getIssueEndTime());

            //1.组织数据
            HashMap<String, String> map = new HashMap<>(4);
            map.put("issueBeginTime",String.valueOf(DateUtils.toEpochMilli(localCoupon.getIssueBeginTime())));
            map.put("issueEndTime",String.valueOf(DateUtils.toEpochMilli(localCoupon.getIssueEndTime())));
            map.put("totalNum",String.valueOf(localCoupon.getTotalNum()));
            map.put("userLimit",String.valueOf(localCoupon.getUserLimit()));
            //2.写缓存
            stringRedisTemplate.opsForHash().putAll(PromotionConstants.COUPON_CACHE_KEY_PREFIX+localCoupon.getId(),map);
        }

        //5. 判断是否需要生成兑换码，优惠券类型必须是兑换码，优惠券状态必须是待发放
        if(localCoupon.getObtainWay()== ObtainType.ISSUE && localCoupon.getStatus() == CouponStatus.DRAFT){
            localCoupon.setIssueEndTime(coupon.getIssueEndTime());
            codeService.asyncGenerateCode(localCoupon);
        }
    }

    @Autowired
    private IUserCouponService userCouponService;

    //查询发放中的优惠券
    @Override
    public List<CouponVO> queryIssuingCoupons() {
        //1. 查询手动领取，并且是发放中的优惠券列表
        List<Coupon> coupons = lambdaQuery()
                .eq(Coupon::getStatus, CouponStatus.ISSUING)//发放中
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)//手动领取
                .list();
        if(CollUtils.isEmpty(coupons)){
            return Collections.emptyList();
        }
        //2. 统计当前用户已经领取的优惠券信息
        List<Long> couponIds = coupons.stream().map(Coupon::getId).collect(Collectors.toList());
        // 2.1 查询当前用户已经离去的优惠券的数据
        List<UserCoupon> userCoupons = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .in(UserCoupon::getCouponId, couponIds)
                .list();
        // 2.2 统计当前用户对优惠券的已领取数量
        Map<Long, Long> issuedMap = userCoupons.stream()
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        //2.3 统计当前用户对优惠券的已经领取并且未使用的数量
        Map<Long, Long> unsedMap = userCoupons.stream()
                .filter(uc -> uc.getStatus() == UserCouponStatus.UNUSED)
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        //3. 封装VO结果
        ArrayList<CouponVO> list = new ArrayList<>(coupons.size());
        for (Coupon c : coupons) {
            //3.1 拷贝PO属性到VO
            CouponVO vo = BeanUtils.copyBean(c, CouponVO.class);
            //3.2 是否可以领取:已经被领取的数量<优惠券总数量 && 当前用户已经领取的数量 < 没人限领的数量
            vo.setAvailable(
                    c.getIssueNum() < c.getTotalNum()
                    && issuedMap.getOrDefault(c.getId(),0L) < c.getUserLimit()
            );
            //3.3 是否可以使用: 当前用户已经领取并且未使用的优惠券数量 > 0
            vo.setReceived(unsedMap.getOrDefault(c.getId(),0L)>0);
            list.add(vo);
        }
        return list;
    }

}
